Close the handle-order class: delete <=>, fix a search-while-appending - #192
Merged
Conversation
Three findings from an adversarial review of the enumeration-order PRs (#174, #176, #181, #186). compose_nested_project_subtree binary-searched `available` while push_back-ing into it, so after the first append the range was unsorted and lower_bound's precondition was violated: a directory already present could be reported absent and appended again, and the final sort had no unique to remove it. A duplicate there means sort_dirs_by_depth parses that Tupfile twice in one run, registering its rules twice -- which now dies at the duplicate-key rejection with an error naming a project that is perfectly valid. Which projects hit it was decided by interning-order accidents, i.e. exactly what this PR series was removing. The search now covers only the sorted prefix, which is sufficient because the rel_ids within one call are distinct, and the sort dedups so the invariant no longer rests on that being true. PR #186 deleted <, >, <= and >= on StringId so that every comparison has to state whether it means handle order or name order. It left two routes open, both verified by compilation against the real header: auto x = (a <=> b) < 0; // compiled struct Node { StringId name; int x; // compiled: member-wise auto operator<=>(Node const&) const = default; }; // built-in enum <=> The second is the dangerous one: a defaulted spaceship on any struct holding a StringId sorts by handle without a single character at the call site saying so. Deleting operator<=> rejects both; equality is untouched. Neither route is used anywhere in the tree today, so this closes the class before it grows a member. prepare_job_launch emitted a job's exported variables in interning-handle order. create_env_block (process-win32.cpp:102) writes that sequence into the environment block verbatim, and Win32 expects it sorted alphabetically. Now emitted by name. Not claimed: the review reported observing handle order in a child's `env` output. That evidence is confounded -- commands run under sh, which re-exports its own environment in its own order, so the block order putup passes is not observable that way. The Win32 block requirement stands on its own and is why this changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA
PR metricsPerformance (gcc example, Linux)
Deterministic signals: instructions (cachegrind-simulated instruction reads — exact across runs, no PMU needed), page faults, peak RSS, and the cachegrind D1/LL miss rates. CPU time is user+sys from time(1). Internal statistics (gcc example, up-to-date dry run)
Counters from Binary size (Linux)
Test coverage (lines)
103 files · 15163/17494 lines covered Deltas vs main@a47fe41f5. Updated for e7788db |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three findings from an adversarial review of the enumeration-order PRs (#174, #176, #181, #186).
A search-while-appending precondition violation (major)
compose_nested_project_subtree(src/cli/context.cpp:463) binary-searchedavailableinside a loop thatpush_backs onto it. After the first append the range is unsorted, sobinary_search's precondition is violated — a directory already present can be reported absent and appended again, and the finalstd::sorthad nouniqueto remove it.A duplicate there means
sort_dirs_by_depthparses that Tupfile twice in one run, registering its rules twice — which now dies at the duplicate-key rejection with an error naming a project that is perfectly valid. Which projects hit it is decided by interning-order accidents: exactly what this PR series set out to remove.The search now covers only the sorted prefix, and the sort dedups, so the invariant no longer rests on the rel_ids within one call happening to be distinct.
The reviewer audited every other insert-while-searching site (
cmd_build.cpp:1324,cmd_show.cpp:577,var_tracking.cpp:22,builder.cpp:1695) — all insert at thelower_boundposition and preserve sortedness. This was the only one.#186 left the class half-closed (minor, but it is the point of #186)
Deleting
<,>,<=,>=onStringIdforces every comparison to say whether it means handle order or name order. Two routes stayed open. Both compile against the real header today:The second is the dangerous one — a defaulted spaceship on any struct holding a
StringIdsorts by handle without a single character at the call site saying so. Deletingoperator<=>rejects both;==is untouched. Verified by compiling probes before and after.Neither route is used anywhere in
src/,include/ortest/today, so this closes the class before it grows a member rather than fixing a live bug.Exported variables reached the child in handle order (minor)
prepare_job_launchemitted a job's exported variables in interning-handle order.create_env_block(process-win32.cpp:102) writes that sequence into the environment block verbatim, and Win32 expects it sorted alphabetically. Now emitted by name.Not claimed: the review reported observing handle order in a child's
envoutput. That evidence is confounded — commands run undersh, which re-exports its own environment in its own order, so the block order putup passes is not observable that way. I checked, and could not reproduce a name-order result through a shell after the fix. The Win32 block requirement stands on its own and is the reason this changed.Also from the review, not acted on here
#174/#176comments still attribute interning order to readdir; that has been false since Return directory entries in name order (fixes #179) #181 maderead_directoryreturn sorted entries. The sorts remain necessary — interning order is still arbitrary — but the stated reason is stale. Left for a docs pass.cmd_show.cpp:602("Top instructions") andoutput.cpp:39("Removed empty dir") both tie-break in handle order in user-facing output. Cosmetic; not reproduced.PathId(include/pup/core/path_id.hpp) is the same kind of handle enum with all relational operators intact. No ordered use exists today, so the same deletion would be free — worth doing, but it is a separate change.Verification
make test: 142392 assertions, 634 cases, 32 e2e shards.make tidy,make iwyuclean.The reviewer also confirmed by mutation that the tests added by #174, #181 and #186 genuinely bite: reverting each production sort in turn makes the corresponding test fail, then restoring it makes them pass. And that POSIX and Win32
read_directoryagree on ordering (both sort UTF-8 bytes after conversion), and that #174's glob order matches tup's, whoseunique(dir, name)index has default BINARY collation.🤖 Generated with Claude Code
https://claude.ai/code/session_01TLob4Ef3qyMd3Dnp9q3XmA